#include "mod-ipv4.hpp"


Record * IPv4Decoder::decode (Packet & pkt, Record & prev)
{
  IPv4Session * ssn;
  IPv4Record &r = this->rec;
  IPv4Hdr * hdr = (IPv4Hdr*) pkt.getCur ();
  int hdrLen = hdr->ipHL * 4;
  Node * src, * dst;
  void * ssnKey;

  if (! r.lock ())
    return NULL;

  if (NULL == hdr || ! pkt.moveCur (hdrLen))
    return NULL;

  r.setData (DEC_IPV4, hdr, hdrLen);
  ssnKey = this->makeSsnKey (&hdr->ipSrc, &hdr->ipDst, ADDR_LEN);

  if (NULL == (ssn = dynamic_cast<IPv4Session*>
	       (this->ssnTable->lookup (ssnKey, ADDR_LEN * 2))))
    {
      ssn = new IPv4Session ();
      ssn->joinTable (*this->ssnTable, ssnKey, ADDR_LEN * 2);

      if (NULL == (src = this->nodeTable->lookup (&hdr->ipSrc, ADDR_LEN)))
	{
	  src = new Node ();
	  src->setTimer (this->getTimer ());
	  src->joinTable (*this->nodeTable, 
			  static_cast<void*>(&hdr->ipSrc), ADDR_LEN);
	}

      if (NULL == (dst = this->nodeTable->lookup (&hdr->ipDst, ADDR_LEN)))
	{
	  dst = new Node ();
	  dst->setTimer (this->getTimer ());
	  dst->joinTable (*this->nodeTable, 
			  static_cast<void*>(&hdr->ipDst), ADDR_LEN);
	}

      ssn->setNode (src, dst, hdr->ipSrc, hdr->ipDst);
    }
  else
    {
      src = ssn->getNode (hdr->ipSrc);
      dst = ssn->getNode (hdr->ipDst);
    }

  r.totalLen = ntohs (hdr->ipTL);
  pkt.setRemain (r.totalLen);
  
  // set timeout by parent function
  ssn->setTimeout (this->getTimer (), SSN_TIMEOUT);

  // set node and session information
  r.setNode (src, dst);
  r.setSession (ssn);

  // set next decoder type and session root
  switch (hdr->ipProto)
    {
    case IPPROTO_TCP:  
      r.setNextDecoder (DEC_TCP); 
      r.setSessionRootPtr (&ssn->root[0]);
      break;

    case IPPROTO_UDP: 
      r.setNextDecoder (DEC_UDP); 
      r.setSessionRootPtr (&ssn->root[1]);
      break;

    case IPPROTO_ICMP: 
      r.setNextDecoder (DEC_ICMP4); 
      r.setSessionRootPtr (&ssn->root[2]);
      break;

    default:
      /* ignore */
      break;
    }

  return &r;
}



NDecFilter * IPv4Decoder::makeFilter (string &name)
{
  IPv4Filter * f = NULL;
  if (name == "ipv4.src_addr")    
    f = new IPv4Filter (IPv4Filter::FT_SRC_ADDR);
  else if (name == "ipv4.dst_addr")
    f = new IPv4Filter (IPv4Filter::FT_DST_ADDR);

  return f;
}

NDecMpxFilter * IPv4Decoder::makeMpxFilter (string &name)
{
  IPv4MpxFilter * f = NULL;

  if (name == "ipv4.src_addr")
    f = new IPv4MpxFilter (IPv4MpxFilter::FT_SRC_ADDR);
  else if (name == "ipv4.dst_addr")
    f = new IPv4MpxFilter (IPv4MpxFilter::FT_DST_ADDR);

  return f;
}

void IPv4Session::setNode 
(Node * _LNode, Node * _RNode, u_int32_t &_LKey, u_int32_t &_RKey)
{
  this->Session::setNode (_LNode, _RNode);
  this->LKey = _LKey;
  this->RKey = _RKey;
}

Node * IPv4Session::getNode (u_int32_t &key)
{
  if (this->LKey == 0 || this->RKey == 0)
    return NULL;

  if (this->LKey == key)
    return this->getLeftNode ();
  else if (this->RKey == key)
    return this->getRightNode ();
  else
    assert (0);
}

//=============================================================================
bool IPv4Filter::setup (string &arg)
{
  string argAddr, argMask ;
  size_t idx = 0;

  try 
    {
      if (string::npos != (idx = arg.find_first_of ("/")))
	{	  
	  unsigned int prefix;
	  u_int32_t tmpMask = ~0;
	  char *e ;
	  argAddr = arg.substr (0, idx);
	  argMask = arg.substr (idx + 1);
	  prefix = strtoul (argMask.c_str (), &e, 0);
	  if (*e != '\0' || prefix > 32)
	    throw string ("Invalid prefix length '" + arg + "'");

	  tmpMask = tmpMask << (32 - prefix);
	  this->mask = htonl (tmpMask);
	}
      else
	{
	  argAddr = arg;
	  this->mask = ~0;
	}

      if (1 != inet_pton (AF_INET, argAddr.c_str (), &(this->addr)))
	throw string ("Invalid Address Format '" + arg + "'");
    }
  catch (string e)
    {
      this->setErrMsg ("[IPv4Filter] " + e);
      return false;
    }      

  return true;
}

void IPv4Filter::check (NDecResult &res)
{
  Record * rec = res.getRecord (DEC_IPV4);
  IPv4Hdr * hdr;
  u_int32_t * tgt = NULL;

  if (NULL == rec)
    return ;

  if (NULL == (hdr = static_cast<IPv4Hdr*> (rec->getPtr ())))
    return ;

  switch (this->type)
    {
    case FT_SRC_ADDR: tgt = &hdr->ipSrc; break;
    case FT_DST_ADDR: tgt = &hdr->ipDst; break;
    default:
      assert (0);
    }

  if (((*tgt & this->mask) ^ (this->addr & this->mask)) > 0)
    this->pushResult (tgt, 4);

  return ;
}


bool IPv4MpxFilter::setup (string &arg, void * p)
{
  u_int32_t addr;
  
  if (string::npos != arg.find_first_of ("/"))
    return false;
  // not supported longest match

  if (1 != inet_pton (AF_INET, arg.c_str (), &addr))
    return false;

  NDecMpxRes * res = this->ipTable.fetch (&addr, sizeof (addr));
  res->addItem (p);
  return true;
}


int IPv4MpxFilter::exec (NDecResult &res)
{
  Record * rec = res.getRecord (DEC_IPV4);
  IPv4Hdr * hdr;
  u_int32_t * tgt = NULL;
  NDecMpxRes * mr;

  if (NULL == rec)
    return false;

  if (NULL == (hdr = static_cast<IPv4Hdr*> (rec->getPtr ())))
    return false;

  switch (this->type)
    {
    case FT_SRC_ADDR: tgt = &hdr->ipSrc; break;
    case FT_DST_ADDR: tgt = &hdr->ipDst; break;
    default:
      assert (0);
    }
  
  if (NULL == (mr = this->ipTable.lookup (tgt, sizeof (u_int32_t))))
    return 0;

  this->setResult (mr);
  return mr->getCount ();
}
